/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.src;
import java.text.*;
import java.util.*;
import java.io.*;
import java.lang.reflect.Modifier;
import org.openide.src.*;
import org.openide.util.NbBundle;
/** A format used to print members of the source hierarchy.
* It is sometimes used for code generation of elements, and also
* for formatting the display names of the nodes representing
* the hierarchy.
* <P>
*
* This format is similar to {@link MessageFormat}.
* It also uses special characters in the pattern and replaces them with strings,
* depending on the code.
* <P>
* For example:
* <p><CODE><PRE>
* ElementFormat fmt = new ElementFormat ("{m} {r} {n} ({p})");
* MethodElement method = getMethodSomewhere ();
* System.out.println (fmt.format (method));
* </PRE></CODE>
* <p>...should print something like this: <code>"public int method(int,char)"</code>
*
* <p>The substitution codes are:
* <UL>
* <LI> <code>{m}</code> Modifiers
* <LI> <code>{n}</code> Name
* <LI> <code>{C}</code> Name of class (with all outerclasses)
* <LI> <code>{f}</code> Full name of element with package
* <LI> <code>{t}</code> Type
* <LI> <code>{r}</code> Return type
* <LI> <code>{s}</code> Superclass
* <LI> <code>{c}</code> Static (for initializers)
* <LI> <code>{p}</code> Parameters with types but not variable names (e.g. <code>"int,char"</code>).
* <LI> <code>{a}</code> Parameters with types and names (e.g. <code>"int x,char c"</code>).
* <LI> <code>{i}</code> Interfaces
* <LI> <code>{e}</code> Exceptions
* </UL>
*
* <P>
* The following table shows which codes may be used
* to format which kinds of element. An asterisk means
* the code may be used, a hyphen means it cannot:
*
* <p><CODE><PRE>
* character | m n f C t r s c p a i e
* ----------------------------------------------------
* Initializer | - - - - - - - * - - - -
* Field | * * * - * - - - - - - -
* Constructor | * * * - - - - - * * - *
* Method | * * * - - * - - * * - *
* Class | * * * * - - * - - - * -
* Interface | * * * * - - - - - - * -
* </PRE></CODE>
*
* <p>The grammar for expressions:
*
* <p><code><pre>
* messageFormatPattern := string ( "{" messageFormatElement "}" string )*
*
* messageFormatElement := simple_argument { "," prefix "," suffix }
*
* messageFormatElement := array_argument { "," prefix "," suffix { "," delim } }
*
* simple_argument := "m" | "n" | "f" | "C" | "t" | "r" | "s" | "c"
*
* array_argument := "p" | "a" | "i" | "e"
*
* prefix := string
*
* suffix := string
*
* delim := string
*
* </pre></code>
*
* <p>Comments on the previous grammar:
* <UL>
* <LI> <code>simple_argument</code> - arguments which are replaced by a single string
* <LI> <code>array_argument</code> - arguments for arrays (parameters, ...)
* <LI> <code>prefix</code> - prefix before the format element if nonempty
* <LI> <code>suffix</code> - suffix after the format element if nonempty
* <LI> <code>delim</code> - delimiter between the members of the array
* <LI> <code>string</code> - a bare string, or enclosed in double quotes if necessary
* (e.g. if it contains a comma)
* </UL>
*
* <P>
* Example formats:
* <UL>
* <LI> For a method which doesn't throw any exceptions: <code>{e,throws ,}</code> => <code>""</code>
* <LI> For a method which throws <code>IOException</code>: <code>{e,throws ,</code>} => <code>"throws IOException"</code>
* <LI> Method parameters #1: <code>{p,,,-}</code> => <code>"int-int-int"</code>
* <LI> Method parameters #2: <code>{p,(,),", "}</code> => <code>"(int, int, int)"</code>
* </UL>
* <p>The default delimiter is a comma.
* <p>This class <em>currently</em> has a default property editor
* in the property editor search path for the IDE.
*
* @author Petr Hamernik
*/
public final class ElementFormat extends Format {
// ============== Static part =================================
/** Serial UID */
static final long serialVersionUID = 3775521938640169753L;
/** Resource bundle for this class. */
static final ResourceBundle bundle = NbBundle.getBundle(ElementFormat.class);
/** Messages localized strings */
private static final String MSG_BAD_PATTERN = bundle.getString("MSG_badPattern");
private static final String MSG_BAD_ARGUMENT = bundle.getString("MSG_badArgument");
private static final String MSG_NO_PARSING = bundle.getString("MSG_noParsing");
/** Magic characters for all kinds of the formating tags.
* The position of the characters is used as index to the following array.
*/
private static final String PROPERTIES_NAMES_INDEX = "mnfCtrscpaie"; // NOI18N
/** Array of names of all kinds properties which could be included
* in the pattern string.
*/
private static final String[] PROPERTIES_NAMES = {
ElementProperties.PROP_MODIFIERS, //m
ElementProperties.PROP_NAME, //n
ElementProperties.PROP_NAME, //f
ElementProperties.PROP_NAME, //C
ElementProperties.PROP_TYPE, //t
ElementProperties.PROP_RETURN, //r
ElementProperties.PROP_SUPERCLASS, //s
ElementProperties.PROP_STATIC, //c
ElementProperties.PROP_PARAMETERS, //p
ElementProperties.PROP_PARAMETERS, //a
ElementProperties.PROP_INTERFACES, //i
ElementProperties.PROP_EXCEPTIONS //e
};
/** Status constants for the parser. */
private static final byte STATUS_OUTSIDE = 0;
private static final byte STATUS_INSIDE = 1;
private static final byte STATUS_RBRACE = 2;
// ================ Non-static part ===============================
/** Pattern - the string which is given in the constructor. */
private String pattern;
/** The current value of "source" property */ // NOI18N
private boolean source;
/** List of parts of the formated string. Elements of this list are
* either String objects either Tag.
*/
private transient LinkedList list;
// ================ Public methods =================================
/** Create a new format.
* See documentation for the class for the syntax of the format argument.
* @param pattern the pattern describing the format
*/
public ElementFormat(String pattern) {
applyPattern(pattern);
source = true;
}
/** Set whether the formating is used for code generation.
* Default value is <CODE>true</CODE>.
* @param source <CODE>true</CODE> means that all Identifier and Type objects
* used in formating are evaluated by <CODE>getSourceName</CODE>
* method. Otherwise (<CODE>false</CODE>) the getFullName() is
* called.
*/
public void setSourceFormat(boolean source) {
this.source = source;
}
/** Test if this format generate strings in source format or fully
* qualified format.
* @return the source flag
*/
public boolean isSourceFormat() {
return source;
}
/** Get the pattern.
* @return the current pattern
*/
public String getPattern() {
return pattern;
}
/** Format an object.
* @param o should be an {@link Element}
* @param toAppendTo the string buffer to format to
* @param pos currently ignored
* @return the same string buffer it was passed (for convenient chaining)
* @throws IllegalArgumentException if the object was not really an <code>Element</code>
*/
public StringBuffer format(Object o, StringBuffer toAppendTo, FieldPosition pos) {
try {
Element element = (Element) o;
Iterator it = list.iterator();
while (it.hasNext()) {
Object obj = it.next();
if (obj instanceof String) {
toAppendTo.append((String)obj);
}
else {
((Tag)obj).format(element, toAppendTo);
}
}
return toAppendTo;
}
catch (ClassCastException e) {
throw new IllegalArgumentException(MSG_BAD_ARGUMENT);
}
}
/** Formats an element.
* @param element the element to be printed
* @return the formatted string using the pattern
*/
public String format(Element element) {
return format(element, new StringBuffer(), null).toString();
}
/** Test whether a property could affect the formatting.
* I.e., if that property would be read due to one of the control codes in the pattern.
* @param prop the property name from {@link ElementProperties}
* @return <code>true</code> if so
*/
public boolean dependsOnProperty(String prop) {
Iterator it = list.iterator();
while (it.hasNext()) {
Object obj = it.next();
if (obj instanceof Tag) {
int index = PROPERTIES_NAMES_INDEX.indexOf(((Tag)obj).kind);
if (PROPERTIES_NAMES[index].equals(prop))
return true;
}
}
return false;
}
/** Don't parse objects.
* @param source ignored
* @param status ignored
* @return <code>null</code> in the default implementation
*/
public Object parseObject (String source, ParsePosition status) {
return null;
}
/** Reads the object and initialize fields. */
private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
s.defaultReadObject();
applyPattern(pattern);
}
// ====================== Private part ===================================
/** Parse the pattern. */
private void applyPattern(String pattern) {
this.pattern = pattern;
list = new LinkedList();
byte status = STATUS_OUTSIDE;
StringTokenizer tokenizer = new StringTokenizer(pattern, "{}", true); // NOI18N
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
switch (status) {
case STATUS_OUTSIDE:
if (token.equals("}")) // NOI18N
throw new IllegalArgumentException(MSG_BAD_PATTERN);
if (token.equals("{")) // NOI18N
status = STATUS_INSIDE;
else
list.add(token);
break;
case STATUS_INSIDE:
if ((token.equals("{")) || (token.equals("}"))) // NOI18N
throw new IllegalArgumentException(MSG_BAD_PATTERN);
list.add(createTag(token));
status = STATUS_RBRACE;
break;
case STATUS_RBRACE:
if (!token.equals("}")) // NOI18N
throw new IllegalArgumentException(MSG_BAD_PATTERN);
status = STATUS_OUTSIDE;
break;
}
}
}
/** Creates the appropriate tag for the given String.
* @param s The string which is between the brackets in the pattern.
* @return the tag object.
*/
private Tag createTag(String s) {
if (s.length() > 0) {
char c = s.charAt(0);
String[] params = new String[0];
if (s.length() > 1) {
if ((s.length() < 2) || (s.charAt(1) != ','))
throw new IllegalArgumentException(MSG_BAD_PATTERN);
params = parseParams(s.substring(2));
}
if ("mnfCtrsc".indexOf(c) != -1) { // NOI18N
switch (params.length) {
case 0: return new Tag(c, "", ""); // NOI18N
case 2: return new Tag(c, params[0], params[1]);
}
}
else if ("paie".indexOf(c) != -1) { // NOI18N
switch (params.length) {
case 0: return new ArrayTag(c, "", "", ", "); // NOI18N
case 2: return new ArrayTag(c, params[0], params[1], ", "); // NOI18N
case 3: return new ArrayTag(c, params[0], params[1], params[2]);
}
}
}
throw new IllegalArgumentException(MSG_BAD_PATTERN);
}
/** Parse the parameters of the tag.
* @param string of the params delimited by commas
* @return the array of the params.
*/
private String[] parseParams(String s) {
StringTokenizer tokenizer = new StringTokenizer(s, ",", true); // NOI18N
ArrayList list = new ArrayList();
StringBuffer token = new StringBuffer();
boolean comma = false;
boolean inString = false;
while (tokenizer.hasMoreTokens()) {
String t = tokenizer.nextToken();
if (inString) {
token.append(t);
if (t.endsWith("\"")) { // NOI18N
if (token.length() > 1)
token.setLength(token.length() - 1);
list.add(token.toString());
token.setLength(0);
inString = false;
comma = true;
}
continue;
}
if (t.equals(",")) { // NOI18N
if (comma)
comma = false;
else
list.add(""); // NOI18N
continue;
}
if (comma)
throw new IllegalArgumentException(MSG_BAD_PATTERN);
String stringToAdd = t;
if (t.startsWith("\"")) { // NOI18N
if ((t.endsWith("\"")) && (t.length() > 1)) { // NOI18N
stringToAdd = (t.length() <= 2) ? "" : t.substring(1, t.length() - 1); // NOI18N
}
else {
token.append(t.substring(1));
inString = true;
continue;
}
}
list.add(stringToAdd);
comma = true;
token.setLength(0);
}
if (!comma)
list.add(""); // NOI18N
String[] ret = new String[list.size()];
list.toArray(ret);
return ret;
}
private static void resolveClassName (StringBuffer sb, ClassElement element) {
ClassElement c = element.getDeclaringClass ();
if (c == null) {
sb.append (element.getName ().getName ());
return;
}
resolveClassName (sb, c);
sb.append ('.').append (element.getName ().getName ());
}
/** Convert the Indentifier to string depending on "source" flag.
*/
String identifierToString(Identifier id) {
return source ? id.getSourceName() : id.getFullName();
}
/** Convert the Type to String depending on "source" flag.
*/
String typeToString(Type id) {
return source ? id.getSourceString() : id.getFullString();
}
/** Tag for simple types - m,n,t,r,s,c */
private class Tag extends Object implements Serializable {
/** Tag character */
char kind;
/** Prefix of the tag */
String prefix;
/** Suffix of the tag */
String suffix;
static final long serialVersionUID =4946774706959011193L;
/** Creates the tag. */
Tag(char kind, String prefix, String suffix) {
this.kind = kind;
this.prefix = prefix;
this.suffix = suffix;
}
/** Formats this tag for the given element.
* @param element Element to be formated.
* @param buf StringBuffer where to add the formated string.
*/
void format(Element element, StringBuffer buf) {
try {
int mark = buf.length();
buf.append(prefix);
switch (kind) {
case 'm':
buf.append(Modifier.toString(((MemberElement)element).getModifiers()));
break;
case 'n':
buf.append(identifierToString(((MemberElement)element).getName()));
break;
case 'f':
buf.append(identifierToString(((MemberElement)element).getName()));
break;
case 'C':
resolveClassName (buf, (ClassElement)element);
break;
case 't':
buf.append(typeToString(((FieldElement)element).getType()));
break;
case 'r':
buf.append(typeToString(((MethodElement)element).getReturn()));
break;
case 's':
Identifier id = ((ClassElement)element).getSuperclass();
if (id != null)
buf.append(identifierToString(id));
break;
case 'c':
if (((InitializerElement)element).isStatic())
buf.append(Modifier.toString(Modifier.STATIC));
break;
}
if (buf.length() > mark + prefix.length()) {
buf.append(suffix);
}
else {
buf.setLength(mark);
}
}
catch (ClassCastException e) {
throw new IllegalArgumentException(MSG_BAD_PATTERN);
}
}
}
/** Tag for arrays - params, exceptions, interfaces.
*/
private class ArrayTag extends Tag {
/** Delimiter */
String delim;
static final long serialVersionUID =2060398944304753010L;
/** Creates new array tag. */
ArrayTag(char kind, String prefix, String suffix, String delim) {
super(kind, prefix, suffix);
this.delim = delim;
}
/** Formats this tag for the given element.
* @param element Element to be formated.
* @param buf StringBuffer where to add the formated string.
*/
void format(Element element, StringBuffer buf) {
try {
int mark = buf.length();
buf.append(prefix);
switch (kind) {
case 'e':
Identifier[] ids = ((ConstructorElement)element).getExceptions();
for (int i = 0; i < ids.length; i++) {
if (i > 0)
buf.append(delim);
buf.append(identifierToString(ids[i]));
}
break;
case 'p':
case 'a':
MethodParameter[] args = ((ConstructorElement)element).getParameters();
for (int i = 0; i < args.length; i++) {
if (i > 0)
buf.append(delim);
if (kind == 'a') {
args[i].getAsString(buf, source);
}
else {
args[i].getType().getAsString(buf, source);
}
}
break;
case 'i':
ids = ((ClassElement)element).getInterfaces();
for (int i = 0; i < ids.length; i++) {
if (i > 0)
buf.append(delim);
buf.append(identifierToString(ids[i]));
}
break;
}
if (buf.length() > mark + prefix.length()) {
buf.append(suffix);
}
else {
buf.setLength(mark);
}
}
catch (ClassCastException e) {
throw new IllegalArgumentException(MSG_BAD_PATTERN);
}
}
}
}
/*
* Log
* 14 src-jtulach1.13 1/12/00 Petr Hamernik i18n using perl script
* (//NOI18N comments added)
* 13 src-jtulach1.12 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 12 src-jtulach1.11 8/17/99 Ian Formanek Generated serial version
* UID
* 11 src-jtulach1.10 7/13/99 Petr Hamernik
* 10 src-jtulach1.9 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 9 src-jtulach1.8 5/13/99 Petr Hamernik bugfix in printing method
* params
* 8 src-jtulach1.7 5/12/99 Petr Hamernik Identifier implementation
* updated
* 7 src-jtulach1.6 4/26/99 Jesse Glick [JavaDoc]
* 6 src-jtulach1.5 4/7/99 Jesse Glick [JavaDoc]
* 5 src-jtulach1.4 4/2/99 Jesse Glick [JavaDoc]
* 4 src-jtulach1.3 3/30/99 Jesse Glick [JavaDoc]
* 3 src-jtulach1.2 3/30/99 Jan Jancura C & f tags added
* 2 src-jtulach1.1 3/23/99 Petr Hamernik
* 1 src-jtulach1.0 3/22/99 Petr Hamernik
* $
*/